package edu.columbia.plt.visage;

import java.awt.Point;
import java.util.*;

import edu.columbia.plt.visage.interfaces.ICell;
import edu.columbia.plt.visage.interfaces.IMatrix;


/*	Interface Matrix
 * 
 *  By Visage Team
 *  Matrix is the collection of cells. 
 *  
 */

public class Matrix implements IMatrix {

	// member variables include:
	private static int width;
	private static int height;
	private static Cell[][] matrix;
	private static Map<String, Map<Integer, Agent>> agentTypeMap;
	private static int cellSize;
	private static Point mouseClickedPoint;
	private static boolean isAPressed;
	private static boolean isSPressed;
	private static boolean isDPressed;
	private static boolean isFPressed;
	private static boolean isUpPressed;
	private static boolean isDownPressed;
	private static boolean isLeftPressed;
	private static boolean isRightPressed;
	private static boolean isMouseClicked;
	
	public static void resetPressed() {
		isAPressed = false;
		isSPressed = false;
		isDPressed = false;
		isFPressed = false;
		isUpPressed = false;
		isDownPressed = false;
		isLeftPressed = false;
		isRightPressed = false;	
	}
	
	
	
	public static void resetMouse(){
		isMouseClicked = false;
	}
	
	public static synchronized boolean isMouseClicked() {
		if (!isMouseClicked)
			return false;
		else{
			isMouseClicked = false;
			return true;
		}
	}
	
	public static synchronized boolean isAPressed() {
		if (!isAPressed)
			return false;
		else{
			isAPressed = false;
			return true;
		}
	}
	
	public static synchronized boolean isSPressed() {
		if (!isSPressed)
			return false;
		else{
			isSPressed = false;
			return true;
		}
	}
	
	public static synchronized boolean isDPressed() {
		if (!isDPressed)
			return false;
		else{
			isDPressed = false;
			return true;
		}
	}
	
	public static synchronized boolean isFPressed() {
		if (!isFPressed)
			return false;
		else{
			isFPressed = false;
			return true;
		}
	}
	
	public static synchronized boolean isUpPressed() {
		if (!isUpPressed)
			return false;
		else{
			isUpPressed = false;
			return true;
		}
	}
	
	public static synchronized boolean isDownPressed() {
		if (!isDownPressed)
			return false;
		else{
			isDownPressed = false;
			return true;
		}
	}
	
	public static synchronized boolean isLeftPressed() {
		if (!isLeftPressed)
			return false;
		else{
			isLeftPressed = false;
			return true;
		}
	}
	
	public static synchronized boolean isRightPressed() {
		if (!isRightPressed)
			return false;
		else{
			isRightPressed = false;
			return true;
		}
	}
	
	public static void aPressedSetTrue() {
		isAPressed = true;
	}
	public static void sPressedSetTrue() {
		isSPressed = true;
	}
	public static void dPressedSetTrue() {
		isDPressed = true;
	}
	public static void fPressedSetTrue() {
		isFPressed = true;
	}
	public static void upPressedSetTrue() {
		isUpPressed = true;
	}
	public static void downPressedSetTrue() {
		isDownPressed = true;
	}
	public static void leftPressedSetTrue() {
		isLeftPressed = true;
	}
	public static void rightPressedSetTrue() {
		isRightPressed = true;
	}
	public static void mouseClickedSetTrue() {
		isMouseClicked = true;
	}
	
	// construction function
	public Matrix(int width, int height, int cellSize) {
		
		Matrix.width = width;
		Matrix.height = height;
		Matrix.cellSize = cellSize;
		matrix = new Cell[width][height];
		for (int i = 0; i < width; i++)
			for (int j = 0; j < height; j++)
				matrix[i][j] = new Cell();
		agentTypeMap = new LinkedHashMap<String, Map<Integer, Agent>>();
	}

	static public void setSize(int width, int height) {
		Matrix.width = width;
		Matrix.height = height;
		matrix = new Cell[width][height];
		for (int i = 0; i < width; i++)
			for (int j = 0; j < height; j++)
				matrix[i][j] = new Cell();
	} // set Matrix size

	static public void setCellSize(int cellSize) {
		Matrix.cellSize = cellSize;
	}

	static public int getWidth() {
		return width;
	}

	static public int getHeight() {
		return height;
	}

	static public int getCellSize() {
		return cellSize;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see visage.IMatrix#getMatrixHeightInPixels()
	 */
	public int getMatrixHeightInPixels() {
		return height * cellSize;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see visage.IMatrix#getMatrixWidthInPixels()
	 */
	public int getMatrixWidthInPixels() {
		return width * cellSize;
	}

	static public Cell getCellByIndex(double x, double y) {
		int rx = (int) Math.floor(x);
		int ry = (int) Math.floor(y);
		return matrix[rx][ry];
	} // get certain cell

	static public Collection<Cell> getNeighbors(double x, double y) {
		int rx = (int) Math.floor(x);
		int ry = (int) Math.floor(y);
		Collection<Cell> neighbors = new HashSet<Cell>();
		neighbors.add(matrix[rx][ry]);
		if (rx > 0 && ry > 0)
			neighbors.add(matrix[rx - 1][ry - 1]);
		if (rx > 0)
			neighbors.add(matrix[rx - 1][ry]);
		if (rx > 0 && ry < height - 1)
			neighbors.add(matrix[rx - 1][ry + 1]);
		if (ry > 0)
			neighbors.add(matrix[rx][ry - 1]);
		if (ry < height - 1)
			neighbors.add(matrix[rx][ry + 1]);
		if (rx < width - 1 && ry > 0)
			neighbors.add(matrix[rx + 1][ry - 1]);
		if (rx < width - 1)
			neighbors.add(matrix[rx + 1][ry]);
		if (rx < width - 1 && ry < height - 1)
			neighbors.add(matrix[rx + 1][ry + 1]);

		return neighbors;
	} // get neighbors around certain cell

	static public synchronized void addAgent(Agent agent) {
		if (!agentTypeMap.containsKey(agent.getType())) {
			agentTypeMap.put(agent.getType(),
					new LinkedHashMap<Integer, Agent>());
		}
		Map<Integer, Agent> agentMap = agentTypeMap.get(agent.getType());
		agentMap.put(agent.getID(), agent);
		//System.out.printf("addAgent: %sMapSize %d\n", agent.getType(), agentTypeMap.get(agent.getType()).size());
	}

	static public synchronized void removeAgentByID(int ID) {
		for (String agentType : agentTypeMap.keySet()) {
			Map<Integer, Agent> agentMap = agentTypeMap.get(agentType);
			agentMap.remove(ID);
		}
	}

	static public  synchronized Map<String, Integer> getAgentTypeCounts() {
		Map<String, Integer> agentTypeCount = new LinkedHashMap<String, Integer>();
		for ( String agentType : agentTypeMap.keySet()) {
			Map<Integer, Agent> agentMap = agentTypeMap.get(agentType);
			agentTypeCount.put(agentType, agentMap.size());
		}
		return agentTypeCount;
	}
	static public synchronized List<Agent> getAgentList() {
		ArrayList<Agent> agentList = new ArrayList<Agent>();
		for (String agentType : agentTypeMap.keySet()) {
			Map<Integer, Agent> agentMap = agentTypeMap.get(agentType);
			agentList.addAll(agentMap.values());
		}
		return agentList;
	}

	static public synchronized Collection<Agent> getAgentSetByType(String type) {
		ArrayList<Agent> agentList = new ArrayList<Agent>();
		Map<Integer, Agent> agentMap = agentTypeMap.get(type);
		if (agentMap != null) {
			agentList.addAll(agentMap.values());
		}
		//System.out.println("getByType: MapSize: "+agentMap.size());
		//System.out.println("getByType: ListSize: "+agentList.size());
		return agentList;
	}

	
	public static void setMouseClickedPoint(Point p) {  // this is called by matrixdraw
		mouseClickedPoint = p;
	}
	
	
	
	public static double getMouseClickedPointX(){
		return mouseClickedPoint.getX()/cellSize;
	}
	
	public static double getMouseClickedPointY(){
		return mouseClickedPoint.getY()/cellSize;
	}
	
	public static synchronized List<Agent> getAgentSetNearMouse() {
		Collection<Cell> cells = new ArrayList<Cell>();
		List<Agent> agents = new ArrayList<Agent>();
		
		if ( mouseClickedPoint == null ) {  //return empty list if mouse wasnt clicked
			return agents;
		}
		System.out.println(mouseClickedPoint);
		cells = Matrix.getNeighbors(mouseClickedPoint.getX()/cellSize, mouseClickedPoint.getY()/cellSize);  // get neighbors and itself	
		for ( Cell cell : cells) {
			agents.addAll(cell.getAllAgents());
		}
		
		mouseClickedPoint = null;
		return agents;
	}
	
	public static synchronized List<Agent> getAgentSetByTypeNearMouse(String type) {
		Collection<Cell> cells = new ArrayList<Cell>();
		List<Agent> agents = new ArrayList<Agent>();
		
		if ( mouseClickedPoint == null ) {  //return empty list if mouse wasnt clicked
			return agents;
		}
		System.out.println(mouseClickedPoint);
		cells = Matrix.getNeighbors(mouseClickedPoint.getX()/cellSize, mouseClickedPoint.getY()/cellSize);  // get neighbors and itself	
		
		for ( Cell cell : cells) {
			agents.addAll(cell.getAgentsByType(type));
		}
		
		mouseClickedPoint = null;
		return agents;
	}
	
	public static synchronized void eraseTracks(){
		List<Agent> agents = Matrix.getAgentList();
		for ( Agent agent : agents){
			agent.trackoff();
		}
	}
	
	public static double picka(Collection<Agent> agents, String agentVariable){
		if(agents == null)
			return 0;
		double value = 0;
		for (Agent agent: agents){
			
			value = agent.getVariable(agentVariable);
			if (Math.random() > 0.5)
				break;
		}
		return value;
	}
	
	public static double average(Collection<Agent> agents, String agentVariable){
		if(agents == null)
			return 0;
		double value = 0;
		for (Agent agent: agents)
			value += agent.getVariable(agentVariable);
		return value/agents.size();
	}
	
	public static double max(Collection<Agent> agents, String agentVariable){
		if(agents == null)
			return 0;
		int i = 0;
		double value = 0;
		for (Agent agent: agents){
			double tmp = agent.getVariable(agentVariable);
			if (i == 0){
				value = tmp;
				i++;
			}
			if (tmp >= value)
				value = tmp;
		}
		return value;
	}
	
	public static double min(Collection<Agent> agents, String agentVariable){
		if(agents == null)
			return 0;
		int i = 0;
		double value = 0;
		for (Agent agent: agents){
			double tmp = agent.getVariable(agentVariable);
			if (i == 0){
				value = tmp;
				i++;
			}
			if (tmp <= value)
				value = tmp;
		}
		return value;
	}
	
	public static double randomBetween(double first, double second){
		return Math.min(first, second) + Math.random()*Math.abs(first - second);
	}
	/*
	 * (non-Javadoc)
	 * 
	 * @see visage.IMatrix#print()
	 */
	public void print() {
		for (String agentType : agentTypeMap.keySet()) {
			Map<Integer, Agent> agentMap = agentTypeMap.get(agentType);
			for (int id : agentMap.keySet()) {
				agentMap.get(id).print();
			}
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see visage.IMatrix#draw()
	 */
	public void draw() {

	} // draw Matrix
}
